﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using System.Windows.Media.Imaging;

namespace OneByOneSolitaire
{
    public partial class MainPage : PhoneApplicationPage
    {
        private static int m_ballImage; // use by settings to set the image for balls
        private static bool m_moveDiagonalAllowed; // use by settings to set if move diagonal is allowed

        private Ball[] m_balls = new Ball[32]; // balls collections
        private Rectangle[] m_boxes = new Rectangle[33]; // coordinates of every hole in the board
        private int[,] m_board = new int[7, 7]; // flag to determine if there is a ball in a hole

        private bool m_firstMove; // flag to know if we need to create a new game
        private int m_pl_id; // current player id
        private string m_pl_name; // current player name
        private static int m_gm_id; // current game id
        private static int m_last_vm_id; // used to return from settings to the last move in a game

        private DataBase m_db; // our database object

	    // moves - used to move forward and backward through the game
        private int m_first_move_id;
        private int m_last_move_id;
        private int m_last_move_x1;
        private int m_last_move_y1;
        private int m_last_move_x2;
        private int m_last_move_y2;

        private Ball m_ballToFade;
        private Ball m_ballToMove;
        private Rectangle m_newPosition;
        private bool m_inAnimation = false;

        // left of every hole
        //
        private int[] posX = new int[33]
                                {  173, 126, 79, 
                                   173, 126, 79,
                         267, 220, 173, 126, 79, 33, -14, 
                         267, 220, 173, 126, 79, 33, -14, 
                         267, 220, 173, 126, 79, 33, -14,
                                   173, 126, 79, 
                                   173, 126, 79};

        // top of every hole
        //
        private int[] posY = new int[33]
                                { 78,  78,  78, 
                                 124, 124, 124, 
                       170, 170, 170, 170, 170, 170, 170, 
                       216, 216, 216, 216, 216, 216, 216,
                       262, 262, 262, 262, 262, 262, 262,
                                 308, 308, 308,
                                 354, 354, 354};

        // X coordinate of every hole
        //
        private int[] cordX = new int[33]
                                   { 4, 3, 2,
							         4, 3, 2,
					           6, 5, 4, 3, 2, 1, 0,
					           6, 5, 4, 3, 2, 1, 0,
					           6, 5, 4, 3, 2, 1, 0, 
							         4, 3, 2,
							         4, 3, 2};

        // Y coordinate of every hole
        //
        private int[] cordY = new int[33]
                                   { 0, 0, 0,
							         1, 1, 1,
					           2, 2, 2, 2, 2, 2, 2,
					           3, 3, 3, 3, 3, 3, 3,
					           4, 4, 4, 4, 4, 4, 4,
							         5, 5, 5,
							         6, 6, 6};

        // setters and getters for settings
        //
        public static int ballImage { get { return m_ballImage; } set { m_ballImage = value; } }
        public static bool moveDiagonalAllowed { get { return m_moveDiagonalAllowed; } set { m_moveDiagonalAllowed = value; } }
        public static int gameId { get { return m_gm_id; } set { m_gm_id = value; } }
        
        // called from settings when the user choose to view a saved game
        // if the user doesn't choose a saved game we will move the game
        // until m_last_move_id == m_last_vm_id which is not necesarily the
        // last move saved in the database because the user could press the
        // undo button one or more times before moving to the settings page
        //
        public static void resetLastMove()
        {
            m_last_vm_id = 0;
        }

        // Constructor
        public MainPage()
        {
            InitializeComponent();

            // Set the data context of the listbox control to the sample data
            DataContext = App.ViewModel;
            this.Loaded += new RoutedEventHandler(MainPage_Loaded);

            // Boxes
            for (int i = 0; i < 33; i++)
            {
                m_boxes[i] = new Rectangle(posX[i], posY[i], 47, 47);
            }
            
            m_db = new DataBase(DataBase.DBConnectionString);
            getPlayer();
            deal();
            loadGame();
        }

        // Load data for the ViewModel Items
        private void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            if (!App.ViewModel.IsDataLoaded)
            {
                App.ViewModel.LoadData();
            }
        }

        // start a new game
        //
        private void cmdDeal_Click(object sender, RoutedEventArgs e)
        {
            deal();
        }

        private void deal()
        { 
	        // moves
	        m_firstMove = true;
	        m_first_move_id = 0;
	        m_last_move_id = 0;

	        cmdUndo.IsEnabled = false;
	        cmdRedo.IsEnabled = false;
	
	        // bals
	        ImageSource image = getImage();

	        for (int i = 0; i < 16; i++) {
		
		        Ball ball;
		        if (m_balls[i] == null) {
			        ball = new Ball(this, image, board);
			        ball.x = cordX[i];
			        ball.y = cordY[i];
			        ball.lastX = 0;
			        ball.lastY = 0;
			        m_board[ball.x, ball.y] = 1;
			        m_balls[i] = ball;
			        ball.position = new Rectangle(posX[i], posY[i], 55, 54);
		        }
		        else {
			        ball = m_balls[i];
			        ball.x = cordX[i];
			        ball.y = cordY[i];
			        m_board[ball.x, ball.y] = 1;
			        ball.position = new Rectangle(posX[i], posY[i], 55, 54);
			        showBall(ball);
			        ball.setImage(image);
		        }		
	        }	
	        for (int i = 17; i < 33; i++) {
		
		        Ball ball;
		        if (m_balls[i-1] == null) {
                    ball = new Ball(this, image, board);
			        ball.lastX = 0;
			        ball.lastY = 0;
			        ball.x = cordX[i];
			        ball.y = cordY[i];
			        m_board[ball.x, ball.y] = 1;
			        m_balls[i-1] = ball;
                    ball.position = new Rectangle(posX[i], posY[i], 55, 54);
		        }
		        else {
			        ball = m_balls[i-1];
			        ball.x = cordX[i];
			        ball.y = cordY[i];
			        m_board[ball.x, ball.y] = 1;
			        ball.position = new Rectangle(posX[i], posY[i], 55, 54);
			        showBall(ball);
			        ball.setImage(image);
		        }		
	        }	
	        m_board[3, 3] = 0;
        }

        // save a move in the database
        //
        private void saveMoveX1(int x1, int y1, int x2, int y2) 
        {
	        // because the player can undo moves, we need to delete every move
	        // after the current move to start a new set of moves
	        //
	        if (!m_db.deleteMoves(m_last_move_id, m_gm_id)) 
            {
		        showAlert(m_db.getLastErrorMsg());
	        }
            
            Move move = m_db.createMove(x1, y1, x2, y2, m_gm_id);
	        if (move == null) 
            {
		        showAlert(m_db.getLastErrorMsg());
	        }
	        else 
            {
		        m_last_move_id = move.mv_id;
		        if (m_first_move_id == 0)
			        m_first_move_id = m_last_move_id;

                // the last move by the user in this game
                // used to return from settings
                //
                m_last_vm_id = m_last_move_id;
	        }
        }

        // if something goes wrong it shows a messagbox
        //
        private void showAlert(string msg) 
        {
			MessageBox.Show(msg, "Database Error", MessageBoxButton.OK);
        }

        // translate m_ballImage index to image ball name
        //
        private ImageSource getImage ()
        {
            string ballImageName = "Images/";
	        switch (m_ballImage) {
		        case 0:
			        ballImageName += "bolita-blanca-celeste-amarilla-sombra.png";
			        break;
                case 1:
                    ballImageName += "bolita-azul-vidrio-sombra.png";
                    break;
                case 2:
                    ballImageName += "bolita-lechera-sombra.png";
                    break;
                case 3:
                    ballImageName += "bolita-madera-oscura-sombra.png";
                    break;
                case 4:
			        ballImageName += "bolita-madera-clara-sombra.png";
			        break;
		        case 5:
			        ballImageName += "bolita-vidrio-sombra.png";
			        break;
                case 6:
                    ballImageName += "bolita-sombra.png";
                    break;
                case 7:
			        ballImageName += "bolita-blanco-negro-sombra.png";
			        break;
		        default:
			        break;
	        }
            return new BitmapImage(new Uri(ballImageName, UriKind.Relative));
        }

        // if a move results invalid it function restore the ball 
        // to their original position
        //
        public void setBallOriginalPosition(Ball ball) 
        {
	        int x = -1, y = -1;
	
	        for (int i = 0; i < 33; i++) {
		        if (cordX[i] == ball.x) {
			        x = i;
			        break;
		        }						
	        }
	        for (int i = 0; i < 33; i++) {
		        if (cordY[i] == ball.y) {
			        y = i;
			        break;
		        }						
	        }
	
	        if (x >= 0 && y >= 0) {
                restorePosition(ball, new Rectangle(posX[x], posY[y], 55, 54));
	        }
        }

        // undo a move
        //
        private void undo ()
        {
	        getLastMove();
	        if (m_last_move_id != 0) {

		        int x, y;
		        if (m_last_move_x1 != m_last_move_x2) {
			        if (m_last_move_x1 > m_last_move_x2)
				        x = m_last_move_x1 - 1;
			        else
				        x = m_last_move_x2 - 1;
		        }
		        else 
			        x = m_last_move_x2;

		        if (m_last_move_y1 != m_last_move_y2) {		
			        if (m_last_move_y1 > m_last_move_y2)
				        y = m_last_move_y1 - 1;
			        else
				        y = m_last_move_y2 - 1;
		        }
		        else 
			        y = m_last_move_y2;
		
		        for (int k = 0; k < 32; k++) {
			        if (m_balls[k].lastX == x && m_balls[k].lastY == y) {
				        showBall(m_balls[k]);
				        m_balls[k].x = x;
				        m_balls[k].y = y;
				        m_balls[k].lastX = 0;
				        m_balls[k].lastY = 0;
				        m_board[x, y] = 1;
				        break;
			        }
		        }
		        m_board[m_last_move_x2, m_last_move_y2] = 0;
		        m_board[m_last_move_x1, m_last_move_y1] = 1;

		        for (int k = 0; k < 32; k++) {
			        if (m_balls[k].x == m_last_move_x2 && m_balls[k].y == m_last_move_y2) {
				        m_balls[k].x = m_last_move_x1;
				        m_balls[k].y = m_last_move_y1;
				        m_balls[k].restoreToInitialPosition();
				        break;
			        }
		        }
		
		        cmdRedo.IsEnabled = true;
		
		        // we move back
		        //
		        m_last_move_id = getPreviousMoveId(m_last_move_id);
		        // if we have undo the first move
		        //
		        if (m_last_move_id < m_first_move_id) {
			        m_first_move_id = 0;
			        m_last_move_id = 0;
			        cmdUndo.IsEnabled = false; 
		        }
	        }
	        else {
		        cmdUndo.IsEnabled = false;
	        }
        }

        // redo a move
        //
        private bool redo()
        {
            bool rtn = true;
	        
            // we move forward
	        //
	        int mv_id = getNextMoveId(m_last_move_id);
	        
            // if we have redoed the last move
	        //
	        if (mv_id == 0) {
		        cmdRedo.IsEnabled = false;
                rtn = false;
	        }
	        else {
		
		        m_last_move_id = mv_id;
		        getLastMove();
		
		        if (m_last_move_id != 0) {
			
			        int x, y;
			        if (m_last_move_x1 != m_last_move_x2) {
				        if (m_last_move_x1 > m_last_move_x2)
					        x = m_last_move_x1 - 1;
				        else
					        x = m_last_move_x2 - 1;
			        }
			        else 
				        x = m_last_move_x2;
			
			        if (m_last_move_y1 != m_last_move_y2) {		
				        if (m_last_move_y1 > m_last_move_y2)
					        y = m_last_move_y1 - 1;
				        else
					        y = m_last_move_y2 - 1;
			        }
			        else 
				        y = m_last_move_y2;
			
			        for (int k = 0; k < 32; k++) {
				        if (m_balls[k].x == x && m_balls[k].y == y) {
					        fadeBall(m_balls[k]);
					        m_balls[k].x = 0;
					        m_balls[k].y = 0;
					        m_balls[k].lastX = x;
					        m_balls[k].lastY = y;
					        m_board[x, y] = 0;
					        break;
				        }
			        }
			        m_board[m_last_move_x2, m_last_move_y2] = 1;
			        m_board[m_last_move_x1, m_last_move_y1] = 0;
			
			        for (int k = 0; k < 32; k++) {
				        if (m_balls[k].x == m_last_move_x1 && m_balls[k].y == m_last_move_y1) {
					        m_balls[k].x = m_last_move_x2;
					        m_balls[k].y = m_last_move_y2;
					        m_balls[k].restoreToInitialPosition();
					        break;
				        }
			        }
			
			        cmdUndo.IsEnabled = true;
			
			        // we must check if there are more moves we can redo
			        //
			        mv_id = getNextMoveId(m_last_move_id);
			        if (mv_id == 0) {
				        cmdRedo.IsEnabled = false;
                        rtn = false;
			        }
		        }	
	        }
            return rtn;
        }

        // get the previous mv_id from database
        //
        private int getPreviousMoveId(int moveId) 
        {
	        int mv_id;
	        if (moveId != 0) {
                mv_id = m_db.getPreviousMoveId(moveId, m_gm_id);
            }
	        else {
		        mv_id = 0;
	        }
	        return mv_id;
        }

        // get the next mv_id from database
        //
        private int getNextMoveId(int moveId) 
        {
	        return m_db.getNextMoveId(moveId, m_gm_id);
        }

        // load the last move id from the database
        // m_last_move_id is not necesarily the last move
        // save in database because undo and redo alter
        // the value of m_last_move_id to go forward and
        // backward through the game
        //
        private void getLastMove ()
        {
	        if (m_last_move_id != 0) {
		        Move move = m_db.getMove(m_last_move_id, m_gm_id);
                if (move != null)
                {
                    m_last_move_x1 = move.mv_x1;
                    m_last_move_y1 = move.mv_y1;
                    m_last_move_x2 = move.mv_x2;
                    m_last_move_y2 = move.mv_y2;
                }
                else 
                {
                    m_last_move_x1 = 0;
                    m_last_move_y1 = 0;
                    m_last_move_x2 = 0;
                    m_last_move_y2 = 0;                
                }
	        }
        }

        // modify the ZOrder of the images to
        // bring to front the ball which is 
        // being moved by the user
        //
        public void startMovingBall(Ball ball) {
            ball.enabled = true;
            Canvas.SetZIndex(ball.imageCtrl, 99); 
            for (int i = 0; i < 32; i++)
            {
                if (m_balls[i] != ball)
                {
                    m_balls[i].enabled = false; // to avoid mousemove events in every other ball
                    Canvas.SetZIndex(m_balls[i].imageCtrl, 0);
                }
            }
        }

        // restore the ZOrder of every ball to 0
        // and enabled every ball to allow mousemove event
        //
        public void endMovingBall() {
            for (int i = 0; i < 32; i++)
            {
                m_balls[i].enabled = true;
                Canvas.SetZIndex(m_balls[i].imageCtrl, 0);
            }
        }

        // if the move is valid move the ball to the new position,
        // hide the ball which was jumped, save the move in database
        // and returns true.
        // if the move is invalid return the returns false.
        //
        public bool move(Ball ball, Point point)
        {
            // if it is the first move we create a new game
            //
	        if (m_firstMove) {
		        m_firstMove = false;
		        saveNewGame();
	        }
	
            // the original position of the ball in the board
            //
	        int originalX, originalY;
	        originalX = ball.x;
	        originalY = ball.y;
	
	        for (int i = 0; i < 33; i++) {
                // we need to know in which hole the user has released
                // the ball 
                //
		        if(	  point.X >= m_boxes[i].x
		           && point.Y >= m_boxes[i].y
		           && point.X <= m_boxes[i].x + m_boxes[i].width
		           && point.Y <= m_boxes[i].y + m_boxes[i].height
		           ) 
                {
                    // if the hole is empty
                    //
			        if (m_board[cordX[i], cordY[i]] == 0) {
				        int moveX, moveY;
				        moveX = ball.x - cordX[i];
				        moveY = ball.y - cordY[i];
				
                        // if the distance between the original position and the new position
                        // is equal 2 or zero the ball has jumped one position. I the distance
                        // between x1 - x2 or y1 - y2 is zero it means that the ball moves vertical
                        // or horizontal but not diagonal. When neither x1 - x2 or y1 - y2 are zero
                        // it means the move was diagonal
                        //
				        if ((Math.Abs(moveX) == 2 || moveX == 0) && (Math.Abs(moveY) == 2 || moveY == 0)) {
                            // we check if the move was diagonal
                            //
					        if (!(Math.Abs(moveX) != 0 && Math.Abs(moveY) != 0) || m_moveDiagonalAllowed) {
						        int x, y;
						        x = ball.x - (moveX / 2);
						        y = ball.y - (moveY / 2);
                                // if there was a ball in the hole jumped
                                //
						        if (m_board[x, y] == 1) {
                                    // set the hole empty
                                    //
							        m_board[x, y] = 0;
                                    // we need to get a reference to the ball to hide it
                                    //
							        for (int k = 0; k < 32; k++) {
								        if (m_balls[k].x == x && m_balls[k].y == y) {
                                            // hide the ball
									        fadeBall(m_balls[k]);
                                            // we need to save the last position of the ball
                                            // for undo and redo actions
                                            //
									        m_balls[k].lastX = m_balls[k].x;
									        m_balls[k].lastY = m_balls[k].y;
                                            // we put the ball out of the board
                                            //
									        m_balls[k].x = 0;
									        m_balls[k].y = 0;
									        break;
								        }
							        }
                                    // the hole where the ball have jumped has a ball now
                                    //
							        m_board[cordX[i], cordY[i]] = 1;
                                    // the hole where the ball was is now empty
                                    //
							        m_board[ball.x, ball.y] = 0;
                                    // update the coordinates of the ball
                                    //
							        ball.x = cordX[i];
							        ball.y = cordY[i];
                                    // put the ball in the new hole
                                    //
							        ball.position = new Rectangle(m_boxes[i].x, m_boxes[i].y, 55, 54);
							
                                    // save the move in database
                                    //
							        saveMoveX1(originalX, originalY, ball.x, ball.y);

                                    // the user can undo this move
                                    //
							        cmdUndo.IsEnabled = true;
							
							        return true;
						        }
						        else {
                                    // the ball has not jumped over another ball
                                    //
							        return false;
						        }
					        }
					        else {
                                // the user move the ball in diagonal but diagonal moves are
                                // not allowed
                                //
						        return false;
					        }
				        }
				        else {
                            // the user move the ball more or less than a valid move
                            // 
					        return false;
				        }
			        }
			        else {
                        // the user drop the ball in hole which is not empty
                        //
				        return false;
			        }
		        }
	        }
            // if we are here the user has moved the ball out of the board
            //
	        return false;
        }

        // load the player from database
        //
        private void getPlayer ()
        {
	        Player player = m_db.getPlayer();
	        if (player != null)
	        {
		        m_pl_name = player.pl_name;
		        m_pl_id = player.pl_id;
	        }
	        else {
                string deviceName = Microsoft.Phone.Info.DeviceStatus.DeviceName;
                player = m_db.createPlayer(deviceName);
		        if (player == null) {
			        showAlert(m_db.getLastErrorMsg());
		        }
		        else {
			        m_pl_id = player.pl_id;
			        m_pl_name = player.pl_name;
		        }
	        }
        }

        // load a game to their final state
        //
        private void loadGame() {
            // we move the game to the last move
            // for saved games it is the last move in database
            // for the current game it is the value of m_last_vm_id
            //
            while (redo()) 
            {
                if (m_last_vm_id > 0)
                {
                    if (m_last_vm_id <= m_last_move_id)
                    {
                        break;
                    }
                }
            }
        }

        // save a new game in database
        //
        void saveNewGame() 
        {
	        int gm_number = getLastGameNumber() + 1;
	        string gm_name = string.Format("Game {0:G5}", gm_number);
            Game game = m_db.createGame(gm_name, gm_number, m_pl_id);
	        if (game == null) {
		        showAlert(m_db.getLastErrorMsg());
	        }
	        else {
		        m_gm_id = game.gm_id;
	        }
        }

        private int getLastGameNumber()
        {
	        return m_db.getLastGameNumber(m_pl_id);
        }

        private void fadeBall(Ball ball) 
        {
            fadeIn(ball);
        }

        private void showBall(Ball ball) 
        {
            fadeOut(ball);
        }

        private void cmdSettings_Click(object sender, RoutedEventArgs e)
        {
            NavigationService.Navigate(new Uri("/Settings.xaml", UriKind.Relative));
        }

        private void cmdUndo_Click(object sender, RoutedEventArgs e)
        {
            if (!m_inAnimation)
            {
                undo();
                m_last_vm_id = m_last_move_id;
            }
        }

        private void cmdRedo_Click(object sender, RoutedEventArgs e)
        {
            if (!m_inAnimation)
            {
                redo();
                m_last_vm_id = m_last_move_id;
            }
        }

        // animations for fade in, fade out and restore position
        //
        private void fadeIn(Ball ball)
        {
            Storyboard ballFadeIn = new Storyboard();
            ballFadeIn.Completed += new EventHandler(fadeInCompleted);

            Duration d = new Duration(TimeSpan.FromSeconds(1));
            DoubleAnimation daFadeIn = new DoubleAnimation();
            daFadeIn.Duration = d;
            daFadeIn.From = 1.00;
            daFadeIn.To = 0.00;

            ballFadeIn.Duration = d;
            ballFadeIn.Children.Add(daFadeIn);
            Storyboard.SetTarget(daFadeIn, ball.imageCtrl);
            Storyboard.SetTargetProperty(daFadeIn, new PropertyPath("Opacity"));

            m_ballToFade = ball;

            ballFadeIn.Begin();
        }

        private void fadeInCompleted(object sender, EventArgs e) {
            if (m_ballToFade == null) 
            {
                m_ballToFade.imageCtrl.Visibility = System.Windows.Visibility.Collapsed;
                m_ballToFade = null;
            }
        }

        private void fadeOut(Ball ball)
        {
            Storyboard ballFadeOut = new Storyboard();
            ballFadeOut.Completed += new EventHandler(fadeOutCompleted);

            Duration d = new Duration(TimeSpan.FromSeconds(1));
            DoubleAnimation daFadeIn = new DoubleAnimation();
            daFadeIn.Duration = d;
            daFadeIn.From = 0.00;
            daFadeIn.To = 1.00;

            ballFadeOut.Duration = d;
            ballFadeOut.Children.Add(daFadeIn);
            Storyboard.SetTarget(daFadeIn, ball.imageCtrl);
            Storyboard.SetTargetProperty(daFadeIn, new PropertyPath("Opacity"));

            m_ballToFade = ball;

            ballFadeOut.Begin();
        }

        private void fadeOutCompleted(object sender, EventArgs e)
        {
            if (m_ballToFade == null)
            {
                m_ballToFade.imageCtrl.Visibility = System.Windows.Visibility.Visible;
                m_ballToFade = null;
            }
        }

        private void restorePosition(Ball ball, Rectangle rectangle)
        { 
            Storyboard moveBall = new Storyboard();
            moveBall.Completed += new EventHandler(moveBallCompleted);

            Duration d = new Duration(TimeSpan.FromSeconds(0.2));
            DoubleAnimation daTop = new DoubleAnimation();
            daTop.Duration = d;
            daTop.From = Canvas.GetTop(ball.imageCtrl);
            daTop.To = rectangle.y;

            moveBall.Duration = d;
            moveBall.Children.Add(daTop);
            Storyboard.SetTarget(daTop, ball.imageCtrl);
            Storyboard.SetTargetProperty(daTop, new PropertyPath("(Canvas.Top)"));

            DoubleAnimation daLeft = new DoubleAnimation();
            daLeft.Duration = d;
            daLeft.From = Canvas.GetLeft(ball.imageCtrl);
            daLeft.To = rectangle.x;

            moveBall.Duration = d;
            moveBall.Children.Add(daLeft);
            Storyboard.SetTarget(daLeft, ball.imageCtrl);
            Storyboard.SetTargetProperty(daLeft, new PropertyPath("(Canvas.Left)"));
            moveBall.Begin();

            m_newPosition = rectangle;
            m_ballToMove = ball;
            m_inAnimation = true;
        }

        private void moveBallCompleted(object sender, EventArgs e)
        {
            if (m_ballToMove == null)
            {
                m_ballToMove.frame = m_newPosition;
                m_newPosition = null;
                m_ballToMove = null;
            }
            m_inAnimation = false;
        }
    }

}